import struct
import math
import numpy as np

class blockAnalyzer(object):
    def __init__(self):
        'do nothing'

    def get_rms(self, block ):
        # RMS amplitude is defined as the square root of the 
        # mean over time of the square of the amplitude.
        sum_squares = 0.0    

        # iterate over the block.
        for sample in block.samples:
            sum_squares += sample*sample
    
        return math.sqrt( sum_squares / block.NUMBER_OF_SAMPLES )

    def get_frequency_index (self,block,freq):
        #retorna el indice del vector FFT que contiene la frecuencia deseada
        return min(freq / block.FREQUENCY_RESOLUTION, (block.NUMBER_OF_SAMPLES / 2) -1)

    def get_FFT_Frequency(self, block, freq ):
        #Retorna el valor de la transformada rapida de fourier para una frecuencia
        freqIndex = self.get_frequency_index(block,freq)
        return self.get_fft_amplitudes( block )[freqIndex]
        #return block
    
    def get_fft_amplitudes(self, block):
        myFFT = np.fft.rfft(block.samples)
        myFFT = np.real( myFFT )
        myFFT = abs( myFFT )    
        # scale by the number of points so that the magnitude
        #does not depend on the length of the signal or on its sampling frequency
        myFFT = myFFT / float(block.NUMBER_OF_SAMPLES)    
        #myFFT = myFFT**2    # square it to get the power
        return myFFT
        
        
    def get_frecuencia_notas(self, freqBase):
        #Devuelve un vector de semitonos
        #Si la freqBase es 440 entonces tenemos:
        #Vec[0:12] = LA,LA#,SI,DO,DO#,RE,RE#,MI,FA,FA#,SOL,SOL#,LA
        freqSeparacion = math.pow( 2.0, (1.0/12) )
        #La raiz doceaba de 2 separa dos semitonos. si multiplico una frecuencia doce veces por
        #ese valor, la frecuencia se duplica, resultando en el incremento de una octava.
        vecNotas = [freqBase,0,0,0,0,0,0,0,0,0,0,0,0]
        for n in range(0,12):
            vecNotas[n+1] = 1.0 * vecNotas[n] * freqSeparacion
        return vecNotas
            
        
class DeCodificador(object):
    # Recibe una serie de codigos entre 0 y 7 y genera una seria de Bytes entre 0 y 255
    def __init__(self):
        'Constantes de Codificacion'
        self.END_OF_BYTE = 0
        self.VALUE_REPEATED = 1     #codigo actual = ultimo codigo recibido
        self.VALUE_00 = 2       #van 4 de estos por byte
        self.VALUE_01 = 3       #van 4 de estos por byte
        self.VALUE_10 = 4       #van 4 de estos por byte
        self.VALUE_11 = 5       #van 4 de estos por byte
        self.DATA_END = 6
        self.DATA_START = 7
        
        'Variables'
        self.refreshBuffers()
        self.fileObtained = None
        self.listening = False
        
    def dataStart(self):
        self.refreshBuffers()
        self.fileObtained = None
        self.listening = True
    
    def refreshBuffers(self):
        self.byteBuffer = []
        self.streamBuffer = []
        self.lastCodeReceived = self.END_OF_BYTE = 0

    
    def dataEnd(self):
        if len(self.streamBuffer) == 0:
            #lo acabo de volcar a fileObtained, no hago nada
            return
        print('EOF. TODO: Pasar de streamBuffer a fileObtained. Guardarlo a discco ?')
        # algo con struct.unpack('B', data[0])[0]        ...
        self.fileObtained = self.streamBuffer
        self.refreshBuffers()
        self.listening = False
        
    def imprimirArchivo(self):
        print(str(self.fileObtained))
        
    def getArchivo(self):
        return self.fileObtained
        
    def errorHappened(self, code=-1, moreInfo=''):
        if self.listening:
            if moreInfo != '': moreInfo = ': ' + moreInfo
            print('error decodificando codigo ' + str(code) + moreInfo)
            print('byteBuffer: ' + str(self.byteBuffer))
        self.byteBuffer = []
        
    def push(self, code):
        if code < 0 or code > 7:
            self.errorHappened(code,'codigo no es de tipo valido, Byte Ignorado')
            return
        if code == self.lastCodeReceived:
            #no cambio el estado, no hago nada
            return
        if code == self.VALUE_REPEATED:
            code = self.lastCodeReceived
            self.lastCodeReceived = self.VALUE_REPEATED
        else:
            self.lastCodeReceived = code
        if code == self.END_OF_BYTE:
            if len(self.byteBuffer) == 1:
                #es un caracter de control
                if self.byteBuffer[0] == self.DATA_START:
                    self.byteBuffer = []
                    self.dataStart()
                    return
                if self.byteBuffer[0] == self.DATA_END:
                    self.byteBuffer = []
                    self.dataEnd()
                    return
                self.errorHappened(code,'Byte de 2 bits no es de control,  Byte Ignorado')
                return
            if len(self.byteBuffer) == 4:
                for iterCode in self.byteBuffer: 
                    if (iterCode < self.VALUE_00 or iterCode > self.VALUE_11):
                        #no es de datos, la tiro
                        self.errorHappened(code,'Byte de 8 bits no es de datos,  Byte Ignorado')
                        return
                self.consumeByte()
                return
            #Tamanio de palabra erroneo, la tiro. 
            self.errorHappened(code,'Byte de tama;o Incorrecto,  Byte Ignorado')
            return
        #no es un end of byte, lo meto al buffer
        self.byteBuffer.append(code)
        
    def consumeByte(self):
        number = 0  #Quiero un unsigned int de 8 bits            
        
        for x in range (0,4):
            iterCode = self.byteBuffer[x]
            weight = 2**((3-x)*2)
            if iterCode == self.VALUE_00: codeValue = 0
            if iterCode == self.VALUE_01: codeValue = 1
            if iterCode == self.VALUE_10: codeValue = 2
            if iterCode == self.VALUE_11: codeValue = 3
            number = number + codeValue * weight
        self.streamBuffer.append(number)       
        self.byteBuffer = []


class Codificador(object):
    # Recibe una serie de codigos entre 0 y 7 y genera una seria de Bytes entre 0 y 255
    def __init__(self):
        'Constantes de Codificacion'
        self.END_OF_BYTE = 0
        self.VALUE_REPEATED = 1     #codigo actual = ultimo codigo recibido
        self.VALUE_00 = 2       #van 4 de estos por byte
        self.VALUE_01 = 3       #van 4 de estos por byte
        self.VALUE_10 = 4       #van 4 de estos por byte
        self.VALUE_11 = 5       #van 4 de estos por byte
        self.DATA_END = 6
        self.DATA_START = 7
        
        'Variables'
        self.refreshBuffers()
        self.fileGenerated = None
        self.decoding = False
        
    def dataStart(self):
        self.refreshBuffers()
        self.fileGenerated = None
        self.decoding = True
        self.streamBuffer.append(self.END_OF_BYTE)
        self.streamBuffer.append(self.DATA_START)
        self.streamBuffer.append(self.END_OF_BYTE)
        self.streamBuffer.append(self.DATA_START)
        self.streamBuffer.append(self.END_OF_BYTE)
    
    def refreshBuffers(self):
        self.streamBuffer = []
        self.lastCodeWritten = self.END_OF_BYTE

    
    def dataEnd(self):
        if len(self.streamBuffer) == 0:
            #lo acabo de volcar a fileGenerated, no hago nada
            return
        self.streamBuffer.append(self.DATA_END)
        self.streamBuffer.append(self.END_OF_BYTE)
        self.streamBuffer.append(self.DATA_END)
        self.streamBuffer.append(self.END_OF_BYTE)
    
        print('EOF. TODO: Pasar de streamBuffer a fileGenerated. Guardarlo a discco ?')
        # algo con struct.unpack('B', data[0])[0]        ...
        self.fileGenerated = self.streamBuffer
        self.refreshBuffers()
        self.decoding = False
        
    def imprimirArchivo(self):
        print(str(self.fileGenerated))
        
    def getArchivo(self):
        return self.fileGenerated

        
    def errorHappened(self, code=-1, moreInfo=''):
        if self.decoding:
            if moreInfo != '': moreInfo = ': ' + moreInfo
            print('error codificando byte ' + str(code) + moreInfo)
        
    def push(self, byte):        
        if not (byte >= 0 and byte <= 255):
            self.errorHappened(byte,'byte se espera que sea un nro entre 0 y 255')
            return
        for x in range (0,4):
            weight = 2**((3-x)*2)   #64, 16, 4, 1
            twoBits = byte / weight
            byte = byte - weight * twoBits #como son enteros en byte quedo el resto
            if twoBits == 0: code = self.VALUE_00
            if twoBits == 1: code = self.VALUE_01
            if twoBits == 2: code = self.VALUE_10
            if twoBits == 3: code = self.VALUE_11
            if code == self.lastCodeWritten: code = self.VALUE_REPEATED
            self.streamBuffer.append(code)
            self.lastCodeWritten = code
        self.streamBuffer.append(self.END_OF_BYTE)
        self.lastCodeWritten = self.END_OF_BYTE

            
            
               
        
        
        
        
        
        
        
        
        
        
         